﻿using System;
using System.Globalization;


namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Defines format providers and styles for common types and implements formatting logic.
    /// </summary>
    [Serializable]
    public class MessageFormatter : IMessageFormatter
    {
        #region Fields.

        IFormatProvider defaultProvider;
        IFormatProvider intProvider;
        string intFormat;
        NumberStyles intStyle;
        IFormatProvider realProvider;
        string realFormat;
        NumberStyles realStyle;
        IFormatProvider dateProvider;
        string dateFormat;
        DateTimeStyles dateStyle;
        string trueString = bool.TrueString;
        string falseString = bool.FalseString;

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of <b>MessageFormatter</b> class.
        /// </summary>
        /// <remarks>Uses default system format providers for all types.</remarks>
        public MessageFormatter()
        {
            Init(null, null, null, null, null, null, null, NumberStyles.Integer, NumberStyles.Number, DateTimeStyles.None);
        }

        /// <summary>
        /// Initializes a new instance of <b>MessageFormatter</b> class.
        /// </summary>
        /// <param name="defaultProvider">Format provider used for all types. <b>Nullable.</b></param>
        public MessageFormatter(IFormatProvider defaultProvider)
        {
            Init(defaultProvider, null, null, null, null, null, null, NumberStyles.Integer, NumberStyles.Number, DateTimeStyles.None);
        }

        /// <summary>
        /// Initializes a new instance of <b>MessageFormatter</b> class.
        /// </summary>
        /// <param name="defaultProvider">Format provider used for all types unless specific provider for a type is asigned. <b>Nullable.</b></param>
        /// <param name="intFormat">Format for integer values. <b>Nullable.</b></param>
        /// <param name="realFormat">Format for floating point numbers. <b>Nullable.</b></param>
        /// <param name="dateFormat">Format for DateTime values. <b>Nullable.</b></param>
        public MessageFormatter(IFormatProvider defaultProvider, string intFormat, string realFormat, string dateFormat)
        {
            Init(defaultProvider, null, null, null, intFormat, realFormat, dateFormat, NumberStyles.Integer, NumberStyles.Number, DateTimeStyles.None);
        }

        /// <summary>
        /// Initializes a new instance of <b>MessageFormatter</b> class.
        /// </summary>
        /// <param name="defaultProvider">Format provider used for all types unless specific provider for a type is asigned. <b>Nullable.</b></param>
        /// <param name="intProvider">Format provider used to format integer values. <b>Nullable.</b></param>
        /// <param name="realProvider">Format provider used to format floating point numbers. <b>Nullable.</b></param>
        /// <param name="dateProvider">Format provider used to format DateTime values. <b>Nullable.</b></param>
        /// <param name="intFormat">Format for integer values. <b>Nullable.</b></param>
        /// <param name="realFormat">Format for floating point numbers. <b>Nullable.</b></param>
        /// <param name="dateFormat">Format for DateTime values. <b>Nullable.</b></param>
        public MessageFormatter(IFormatProvider defaultProvider, IFormatProvider intProvider, IFormatProvider realProvider, IFormatProvider dateProvider, string intFormat, string realFormat, string dateFormat)
        {
            Init(defaultProvider, intProvider, realProvider, dateProvider, intFormat, realFormat, dateFormat, NumberStyles.Integer, NumberStyles.Number, DateTimeStyles.None);
        }

        /// <summary>
        /// Initializes a new instance of <b>MessageFormatter</b> class.
        /// </summary>
        /// <param name="defaultProvider">Format provider used for all types unless specific provider for a type is asigned. <b>Nullable.</b></param>
        /// <param name="intProvider">Format provider used to format integer values. <b>Nullable.</b></param>
        /// <param name="realProvider">Format provider used to format floating point numbers. <b>Nullable.</b></param>
        /// <param name="dateProvider">Format provider used to format DateTime values. <b>Nullable.</b></param>
        /// <param name="intFormat">Format for integer values. <b>Nullable.</b></param>
        /// <param name="realFormat">Format for floating point numbers. <b>Nullable.</b></param>
        /// <param name="dateFormat">Format for DateTime values. <b>Nullable.</b></param>
        /// <param name="intStyle"><see cref="NumberStyles"/> which indicates the permitted format of strings representing 
        /// integer values. A typical value to specify is <see cref="System.Globalization.NumberStyles.Integer"/>.</param>
        /// <param name="realStyle"><see cref="NumberStyles"/> which indicates the permitted format of strings representing 
        /// floating point numbers. A typical value to specify is <see cref="System.Globalization.NumberStyles.Number"/>.</param>
        /// <param name="dateStyle"><see cref="DateTimeStyles"/> which indicates the permitted format of strings representing 
        /// <b>DateTime</b> values. A typical value to specify is <see cref="DateTimeStyles.None"/>.</param>
        public MessageFormatter(IFormatProvider defaultProvider, IFormatProvider intProvider, IFormatProvider realProvider, IFormatProvider dateProvider,
            string intFormat, string realFormat, string dateFormat, NumberStyles intStyle, NumberStyles realStyle, DateTimeStyles dateStyle)
        {
            Init(defaultProvider, intProvider, realProvider, dateProvider, intFormat, realFormat, dateFormat, intStyle, realStyle, dateStyle);
        }

        private void Init(IFormatProvider defaultProvider, IFormatProvider intProvider, IFormatProvider realProvider, IFormatProvider dateProvider,
            string intFormat, string realFormat, string dateFormat, NumberStyles intStyle, NumberStyles realStyle, DateTimeStyles dateStyle)
        {
            this.defaultProvider = (defaultProvider == null) ? CultureInfo.CurrentCulture : defaultProvider;
            this.intProvider = (intProvider == null) ? this.defaultProvider : intProvider;
            this.realProvider = (realProvider == null) ? this.defaultProvider : realProvider;
            this.dateProvider = (dateProvider == null) ? this.defaultProvider : dateProvider;
            this.intFormat = intFormat;
            this.realFormat = realFormat;
            this.dateFormat = dateFormat;
            this.intStyle = intStyle;
            this.realStyle = realStyle;
            this.dateStyle = dateStyle;
        }

        #endregion

        #region Format.

        /// <summary>
        /// Formats the given value.
        /// </summary>
        /// <param name="val">Value to be formatted.</param>
        /// <returns>String representation of the given value.</returns>
        public string Format(object val)
        {
            if (TypeUtil.IsNull(val))
                return string.Empty;
            else if (val is string)
                return (string)val;
            else if (TypeUtil.IsDateValue(val))
                return Convert.ToDateTime(val).ToString(this.dateFormat, this.dateProvider);
            else if (TypeUtil.IsFloatingPointNumberValue(val))
                return Convert.ToDecimal(val).ToString(this.realFormat, this.realProvider);
            else if (TypeUtil.IsIntegerValue(val))
                return Convert.ToInt64(val).ToString(this.intFormat, this.intProvider);
            else if (val is bool)
                return (bool)val ? this.TrueString : this.FalseString;
            else
                return val.ToString();
        }

        /// <summary>
        /// Formats the given value.
        /// </summary>
        /// <param name="val">Value to be formatted.</param>
        /// <param name="forcedType">Forces the usage of conversion and format specified for the specified type.</param>
        /// <returns>String representation of the given value.</returns>
        public string Format(object val, Type forcedType)
        {
            if (TypeUtil.IsNull(val))
                return string.Empty;
            else if (TypeUtil.IsDate(forcedType))
                return Convert.ToDateTime(val).ToString(this.dateFormat, this.dateProvider);
            else if (TypeUtil.IsFloatingPointNumber(forcedType))
                return Convert.ToDecimal(val).ToString(this.realFormat, this.realProvider);
            else if (TypeUtil.IsInteger(forcedType))
                return Convert.ToInt64(val).ToString(this.intFormat, this.intProvider);
            else if (val is bool)
                return (bool)val ? this.TrueString : this.FalseString;
            else
                return val.ToString();
        }

        #endregion

        #region GetFormat.

        /// <summary>
        /// Gets format used to format values of the given type.
        /// </summary>
        /// <param name="t">Type of value to be formatted.</param>
        /// <returns>Value format.</returns>
        public string GetFormat(Type t)
        {
            if (TypeUtil.IsDate(t))
                return this.dateFormat;
            else if (TypeUtil.IsFloatingPointNumber(t))
                return this.realFormat;
            else if (TypeUtil.IsInteger(t))
                return this.intFormat;
            else
                return null;
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets or sets the format provider used for all types unless specific provider exists. <b>Nullable.</b>
        /// </summary>
        /// <remarks>If set to <b>null</b>, default system format will be used.</remarks>
        public IFormatProvider DefaultProvider
        {
            get { return this.defaultProvider; }
            set { this.defaultProvider = (value == null) ? CultureInfo.CurrentCulture : value; }
        }

        /// <summary>
        /// Gets or sets the format provider used to format integer values. <b>Nullable.</b>
        /// </summary>
        public IFormatProvider IntProvider
        {
            get { return this.intProvider; }
            set { this.intProvider = value; }
        }

        /// <summary>
        /// Gets or sets the format for integer values. <b>Nullable.</b>
        /// </summary>
        public string IntFormat
        {
            get { return this.intFormat; }
            set { this.intFormat = value; }
        }

        /// <summary>
        /// Gets or sets <see cref="NumberStyles"/> which indicates the permitted format of strings representing integer values.
        /// A typical value to specify is <see cref="System.Globalization.NumberStyles.Integer"/>.
        /// </summary>
        public NumberStyles IntStyle
        {
            get { return this.intStyle; }
            set { this.intStyle = value; }
        }

        /// <summary>
        /// Gets or sets the format provider used to format floating point numbers. <b>Nullable.</b>
        /// </summary>
        public IFormatProvider RealProvider
        {
            get { return this.realProvider; }
            set { this.realProvider = value; }
        }

        /// <summary>
        /// Gets or sets the format for floating point numbers. <b>Nullable.</b>
        /// </summary>
        public string RealFormat
        {
            get { return this.realFormat; }
            set { this.realFormat = value; }
        }

        /// <summary>
        /// Gets or sets <see cref="NumberStyles"/> which indicates the permitted format of strings representing floating point numbers.
        /// A typical value to specify is <see cref="System.Globalization.NumberStyles.Number"/>.
        /// </summary>
        public NumberStyles RealStyle
        {
            get { return this.realStyle; }
            set { this.realStyle = value; }
        }

        /// <summary>
        /// Gets or sets the format provider used to format DateTime values. <b>Nullable.</b>
        /// </summary>
        public IFormatProvider DateProvider
        {
            get { return this.dateProvider; }
            set { this.dateProvider = value; }
        }

        /// <summary>
        /// Gets or sets the format for DateTime values. <b>Nullable.</b>
        /// </summary>
        public string DateFormat
        {
            get { return this.dateFormat; }
            set { this.dateFormat = value; }
        }

        /// <summary>
        /// Gets or sets <see cref="DateTimeStyles"/> which indicates the permitted format of strings representing <b>DateTime</b> values.
        /// A typical value to specify is <see cref="DateTimeStyles.None"/>.
        /// </summary>
        public DateTimeStyles DateStyle
        {
            get { return this.dateStyle; }
            set { this.dateStyle = value; }
        }

        /// <summary>
        /// Gets or sets the text returned if <b>true</b> is passed as input parameter to <see cref="Format(object)"/> method.
        /// </summary>
        /// <value>Textual representation of boolean value. Default is <b>True</b>.</value>
        public string TrueString
        {
            get { return this.trueString; }
            set { this.trueString = value; }
        }

        /// <summary>
        /// Gets or sets the text returned if <b>false</b> is passed as input parameter to <see cref="Format(object)"/> method.
        /// </summary>
        /// <value>Textual representation of boolean value. Default is <b>False</b>.</value>
        public string FalseString
        {
            get { return this.falseString; }
            set { this.falseString = value; }
        }

        #endregion
    }
}